package com.syf.aspect;

import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import com.alibaba.fastjson.JSON;
import com.syf.model.WebLog;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.annotation.Order;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

/**
 * @Before : 在切入点开始处切入内容
 * @After  : 在切入点结尾处切入内容
 * @Around : 在切入点前后切入内容，并控制何时执行切入点自身的内容
 * @AfterReturning : 在切入点return内容之后切入内容（可以用来对处理返回值做一些加工处理）
 * @AfterThrowing  : 用来处理当切入内容部分抛出异常之后的处理逻辑
 *
 * 1.JoinPoint对象:
 *    java.lang.Object[] getArgs()：获取连接点方法运行时的入参列表【Object[] 参数值value -> 还需获取参数name】；
 *    Signature getSignature() ：获取连接点的方法签名对象；
 *    java.lang.Object getTarget() ：获取连接点所在的目标对象；
 *    java.lang.Object getThis() ：获取代理对象本身；
 *
 * 2.ProceedingJoinPoint对象
 *      ProceedingJoinPoint对象是JoinPoint的子接口,该对象只用在@Around的切面方法中,
 *      添加了两个方法:
 *          Object proceed() throws Throwable //执行目标方法
 *          Object proceed(Object[] var1) throws Throwable //传入的新的参数去执行目标方法
 *          此方法需要返回值,返回值视为目标方法的返回值
 *
 *  3.异常处理:
 *      try{
 *          执行目标方法
 *      }catch (Throwable e) {
 *             //异常通知
 *             System.out.println("执行目标方法异常...");
 *             throw new RuntimeException(e);
 *      }
 */
@Component //配置类
@Aspect //切面
@Order(1) // 控制多个Aspect的执行顺序，越小越先执行
@Slf4j //日志记录
public class WebLogAspect {

    /**
     * 1 定义切入点(用来确定哪些类需要代理)：
     *      有效范围: com.syf.controller包下所有类的所有方法都会被代理
     */
    @Pointcut("execution(* com.syf.controller.*.*(..))") // controller 包里面所有类，类里面的所有方法 都有该切面
    public void webLog(){}

    /**
     * 2 记录日志的环绕通知：
     *          方法执行之前、之后
     */
    @Around("webLog()")
    public Object recodeWebLog(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        Object result = null ;
        //[1]全局日志实体类
        WebLog webLog = new WebLog();

        //1.计算日志记录该方法执行的时长
        long start = System.currentTimeMillis() ;//开始时间
        result = proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());  // 执行 目标方法(获取Args参数列表) -> 记录调用时间
        long end = System.currentTimeMillis() ; //结束时间
        webLog.setSpendTime((int)(start-end)/1000); // 请求该接口花费的时间
        webLog.setResult(result);

        //2.获取当前请求的request对象
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        webLog.setIp(request.getRemoteAddr()); //IP地址
        webLog.setUri(request.getRequestURI()); // 设置请求的uri

        String url = request.getRequestURL().toString();
        webLog.setUrl(url); //URL
        webLog.setBasePath(StrUtil.removeSuffix(url, URLUtil.url(url).getPath())); //根路径: http://ip:port/

        //3.获取安全的上下文
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        webLog.setUsername(authentication==null ? "anonymous":authentication.getPrincipal().toString()); //操作用户id【anonymous: 匿名用户名(用户未登录)】

        // 4.获取执行的方法
        MethodSignature signature = (MethodSignature)proceedingJoinPoint.getSignature();
        String targetClassName = proceedingJoinPoint.getTarget().getClass().getName();  // 获取类的名称
        Method method = signature.getMethod();

        // 因为我们会使用Swagger 这工具，我们必须在方法上面添加@ApiOperation(value="") 该Swagger对方法的注解
        ApiOperation annotation = method.getAnnotation(ApiOperation.class);  // 获取ApiOperation
        webLog.setDescription(annotation==null ? "no desc":annotation.value());  //执行的方法描述
        webLog.setMethod(targetClassName+"."+method.getName()); //请求的方法名: com.syf.controller.UserController.login()
        webLog.setParameter(getMethodParameter(method,proceedingJoinPoint.getArgs())); //请求参数: {"key_参数的名称":"value_参数的值"}



        //【2.控制台输出 日志】         System.out.println("输出日志----------------");
        log.info(JSON.toJSONString(webLog,true)); //
        return result ;
    }

    /**
     * 获取方法的执行参数
     * @param method
     * @param args
     * @return
     * {"key_参数的名称":"value_参数的值"}
     */
    private Object getMethodParameter(Method method, Object[] args) { //方法对象，Object[] 参数值value
        Map<String, Object> methodParametersWithValues = new HashMap<>();

        //方法的形参名称name
        LocalVariableTableParameterNameDiscoverer localVariableTableParameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();
        String[] parameterNames = localVariableTableParameterNameDiscoverer.getParameterNames(method);

        for (int i = 0; i <parameterNames.length ; i++) {
            if(parameterNames[i].equals("password") || parameterNames[i].equals("file")){
                methodParametersWithValues.put(parameterNames[i],"受限的支持类型") ;
            }else{
                methodParametersWithValues.put(parameterNames[i],args[i]) ;  //参数集合
            }

        }

        return methodParametersWithValues ; //返回集合
    }
}
